package gui;

import engine.Edge;
import engine.Engine;
import engine.Instance;
import engine.Node;
import engine.Time;
import engine.Unit;
import java.awt.Color;
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JTextField;

/**
 *
 * @author Johnny
 */
public class Control implements Serializable {

    private MapPanel mapPanel;
    private Engine engine;
    private Timer timer;
    private MainFrame frame;
    private boolean initialized;

    /**
     * Adds a new edge.
     */
    public void addEdge() {
        Node node1 = (Node) frame.cbEdgeStart.getSelectedItem();
        Node node2 = (Node) frame.cbEdgeEnd.getSelectedItem();
        Color color = frame.btEdgeColor.getForeground();
        engine.addEdge(node1.getId(), node2.getId(), color.getRed(), color.getGreen(),
                color.getBlue());
        edgesPanelSelected();
    }

    /**
     * Updates the selected edge.
     */
    public void updateEdge() {
        Edge selEdge = (Edge) frame.cbEdge.getSelectedItem();
        Node node1 = (Node) frame.cbEdgeStart.getSelectedItem();
        Node node2 = (Node) frame.cbEdgeEnd.getSelectedItem();
        Color color = frame.btEdgeColor.getForeground();
        engine.updateEdge(selEdge.getId(), node1.getId(), node2.getId(),
                color.getRed(), color.getGreen(), color.getBlue());
        fullMap();
    }

    /**
     * Deletes an edge and refreshes the gui.
     */
    public void deleteEdge() {
        Edge selEdge = (Edge) frame.cbEdge.getSelectedItem();
        engine.removeEdge(selEdge.getId());
        edgesPanelSelected();
    }

    /**
     * Adds a middle node to the edge's middle nodes list.
     */
    public void addEdgeMiddleNode() {
        Edge selEdge = (Edge) frame.cbEdge.getSelectedItem();
        Node node = (Node) frame.cbEdgeMnAdd.getSelectedItem();
        engine.addMiddleNode(selEdge.getId(), node.getId());
        fillEdgeData();
        fullMap();
    }

    /**
     * Removes a node from the edge's middle nodes list.
     */
    public void removeEdgeMiddleNode() {
        Edge selEdge = (Edge) frame.cbEdge.getSelectedItem();
        Node node = (Node) frame.cbEdgeMnRemove.getSelectedItem();
        engine.removeMiddleNode(selEdge.getId(), node.getId());
        fillEdgeData();
        fullMap();
    }

    /**
     * Refreshes the GUI for the Units panel.
     */
    public void unitsPanelSelected() {
        simpleMap();
        frame.cbUnit.setModel(new DefaultComboBoxModel(getUnits()));
        fillUnitData();
    }

    /**
     * Fills the unit editing components.
     */
    public void fillUnitData() {
        Unit selUnit = (Unit) frame.cbUnit.getSelectedItem();
        if (selUnit != null) {
            frame.tfUnitName.setText(selUnit.getName());
            frame.cbUnitCpAdd.setModel(new DefaultComboBoxModel(getNodes()));
            frame.cbUnitCpRemove.setModel(new DefaultComboBoxModel(selUnit.getCourse().toArray(new Node[0])));
        }
    }

    /**
     * Adds a new unit.
     */
    public void addUnit() {
        engine.addUnit(frame.tfUnitName.getText());
        unitsPanelSelected();
    }

    /**
     * Updates a unit in the data model.
     */
    public void updateUnit() {
        Unit selUnit = (Unit) frame.cbUnit.getSelectedItem();
        engine.setUnitName(selUnit.getId(), frame.tfUnitName.getText());
    }

    /**
     * Deletes a unit.
     */
    public void deleteUnit() {
        Unit selUnit = (Unit) frame.cbUnit.getSelectedItem();
        engine.removeUnit(selUnit.getId());
        unitsPanelSelected();
    }

    /**
     * Adds a node to the unit's course list.
     */
    public void addUnitCourse() {
        Unit selUnit = (Unit) frame.cbUnit.getSelectedItem();
        Node node = (Node) frame.cbUnitCpAdd.getSelectedItem();
        engine.addUnitCourse(selUnit.getId(), node.getId());
        fillUnitData();
    }

    /**
     * Removes a node from the unit's course list.
     */
    public void removeUnitCourse() {
        Unit selUnit = (Unit) frame.cbUnit.getSelectedItem();
        Node node = (Node) frame.cbUnitCpRemove.getSelectedItem();
        engine.removeUnitCourse(selUnit.getId(), node.getId());
        fillUnitData();
    }

    /**
     * Refreshes the instances panel.
     */
    public void instPanelSelected() {
        simpleMap();
        frame.cbInstUnit.setModel(new DefaultComboBoxModel(getUnits()));
        fillInstData();
    }

    /**
     * Fills the instance data on the gui of a unit.
     */
    public void fillInstData() {
        Unit selUnit = (Unit) frame.cbInstUnit.getSelectedItem();
        if (selUnit != null) {
            frame.cbInst.setModel(new DefaultComboBoxModel(selUnit.getInstances()
                    .toArray(new Instance[0])));
            fillSchedData();
        }
    }

    /**
     * Fills the schedule data on the gui of an instance.
     */
    public void fillSchedData() {
        Unit selUnit = (Unit) frame.cbInstUnit.getSelectedItem();
        Instance inst = (Instance) frame.cbInst.getSelectedItem();
        if (inst != null)
            frame.ttSched.setSchedules(selUnit.getCourse(), inst.getSchedules());
        else
            frame.ttSched.clearSchedules();
    }

    /**
     * Adds a new instance to the unit.
     */
    public void addInstance() {
        Unit selUnit = (Unit) frame.cbInstUnit.getSelectedItem();
        engine.addInstance(selUnit.getId());
        fillInstData();
    }

    /**
     * Removes an instance from the unit.
     */
    public void removeInstance() {
        Unit selUnit = (Unit) frame.cbInstUnit.getSelectedItem();
        Instance inst = (Instance) frame.cbInst.getSelectedItem();
        engine.removeInstance(selUnit.getId(), inst.getId());
        fillInstData();
    }

    /**
     * Updates an instance.
     */
    void updateInstance() {
        Unit selUnit = (Unit) frame.cbInstUnit.getSelectedItem();
        Instance inst = (Instance) frame.cbInst.getSelectedItem();
        engine.setSchedule(selUnit.getId(), inst.getId(), frame.ttSched.getSchedules());
    }

    private class Timer extends Thread {

        private Time startTime;
        private boolean stop;
        private Unit units[];

        public Timer(Time startTime, Unit units[]) {
            this.startTime = startTime;
            this.units = units;
            stop = false;
        }

        @Override
        public void run() {
            while (!stop) {
                try {
                    mapPanel.dispUnits(calcUnitsPos(startTime));
                    sleep(1000);
                    startTime.setTotalSeconds(startTime.getTotalSeconds() + 1);
                } catch (InterruptedException ex) {
                    Logger.getLogger(Control.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }

        /**
         * Calculates the units' positions
         * @return the unit's positions.
         */
        private Node[] calcUnitsPos(Time now) {
            ArrayList<Node> positions = new ArrayList<Node>();
            for (Unit unit : units) {
                positions.addAll(engine.getUnitPos(unit.getId(), now));
            }

            return positions.toArray(new Node[0]);
        }

        public void setStop(boolean stop) {
            this.stop = stop;
        }
    }

    public Control() {
        mapPanel = null;
        engine = new Engine();
        timer = null;
        initialized = false;
    }

    public Control setMapPanel(MapPanel mapPanel) {
        this.mapPanel = mapPanel;
        try {
            ClassLoader cl = this.getClass().getClassLoader(); // Use a ClassLoader to make it compatible with jar files.
            // TODO make it class loadable
            //Image mapImage = ImageIO.read(cl.getResource("res/cluj.png"));
            Image mapImage = ImageIO.read(new File("res\\cluj.png"));
            mapPanel.setMapImage(mapImage);
        } catch (IOException e) {
            System.out.println(e.toString());
        }
        simpleMap();
        initialized = true;
        return this;
    }

    public Control setFrame(MainFrame frame) {
        this.frame = frame;
        return this;
    }

    /**
     * Returns the units.
     * @return the units.
     */
    public Unit[] getUnits() {
        Unit units[] = new Unit[engine.getUnits().size()];
        return engine.getUnits().toArray(units);
    }

    /**
     * Returns the nodes.
     * @return the nodes.
     */
    public Node[] getNodes() {
        return engine.getNodes().toArray(new Node[0]);
    }

    /**
     * Returns the edges.
     * @return the edges.
     */
    public Edge[] getEdges() {
        return engine.getEdges().toArray(new Edge[0]);
    }

    public Engine getEngine() {
        return engine;
    }

    public boolean isInitialized() {
        return initialized;
    }

    public void stopTimer() {
        if (timer != null) {
            timer.setStop(true);
            try {
                timer.join();
            } catch (InterruptedException ex) {
                Logger.getLogger(Control.class.getName()).log(Level.SEVERE, null, ex);
            }
            timer = null;
        }
        mapPanel.dispUnits(new Node[0]); // Remove the displayed units.
        setControls(true);
    }

    /**
     * Starts a map viewer from a specific starting time.
     * @param units the units that will be displayed
     * @param hour the hour.
     * @param minute the minute.
     * @param second the second.
     */
    public void viewMap(Unit units[], int hour, int minute, int second) {
        stopTimer();
        setControls(false);
        timer = new Timer(new Time(hour, minute, second), units);
        timer.start();
    }

    /**
     * Displays a simple map with edges.
     */
    public void simpleMap() {
        mapPanel.setEdges(engine.getEdges().toArray(new Edge[0]));
        mapPanel.refreshImage();
        mapPanel.repaint();
    }

    /**
     * Displays a simple map with edges and nodes, used for an editor.
     */
    public void fullMap() {
        mapPanel.setNodes(engine.getNodes().toArray(new Node[0])).setEdges(engine.getEdges().toArray(new Edge[0]));
        mapPanel.refreshFullImage();
        mapPanel.repaint();
    }

    /**
     * Sets the state of the controls.
     * @param value to this value.
     */
    private void setControls(boolean value) {
        frame.spHour.setEnabled(value);
        frame.spMinute.setEnabled(value);
        frame.spSecond.setEnabled(value);
        frame.btNow.setEnabled(value);
        if (value) {
            frame.btStartStop.setText("Start");
        } else {
            frame.btStartStop.setText("Stop");
        }
        frame.ltUnit.setEnabled(value);
        frame.tplControl.setEnabled(value);
    }

    /**
     * Sets the position of the x and y coordinates that will be used by the
     * editor.
     * @param x the x coordinate.
     * @param y the y coordinate.
     */
    public void setMousePos(int x, int y) {
        float relX = (float) x * 100 / mapPanel.getDim().width;
        float relY = (float) y * 100 / mapPanel.getDim().height;
        JTextField tfx = null, tfy = null;
        if (frame.tplControl.getSelectedComponent() == frame.plNodes) {
            tfx = frame.tfNodeX;
            tfy = frame.tfNodeY;
        }
        if (tfx != null) {
            tfx.setText(Float.toString(relX));
            tfy.setText(Float.toString(relY));
        }
    }

    /**
     * Refresh GUI according to selected panel.
     */
    public void viewPanelSelected() {
        simpleMap();
        frame.ltUnit.setListData(getUnits());
    }

    /**
     * Refresh GUI according to selected panel.
     */
    public void nodesPanelSelected() {
        fullMap();
        frame.cbNodes.setModel(new DefaultComboBoxModel(getNodes()));
        fillNodeData();
    }

    /**
     * Refresh GUI according to selected panel.
     */
    public void edgesPanelSelected() {
        fullMap();
        frame.cbEdge.setModel(new DefaultComboBoxModel(getEdges()));
        Node allNodes[] = getNodes();
        frame.cbEdgeStart.setModel(new DefaultComboBoxModel(allNodes));
        frame.cbEdgeEnd.setModel(new DefaultComboBoxModel(allNodes));
        frame.cbEdgeMnAdd.setModel(new DefaultComboBoxModel(allNodes));
        fillEdgeData();
    }

    /**
     * Feeds the node editing text boxes with data from the selected node of
     * the ComboBox.
     */
    public void fillNodeData() {
        if (frame.cbNodes.getSelectedIndex() != -1) {
            Node selNode = (Node) frame.cbNodes.getSelectedItem();
            frame.tfNodeName.setText(selNode.getName());
            frame.tfNodeX.setText(Float.toString(selNode.getX()));
            frame.tfNodeY.setText(Float.toString(selNode.getY()));
        }
    }

    /**
     * Feeds the edge editing combos with data from the selected node of
     * the ComboBox.
     */
    public void fillEdgeData() {
        if (frame.cbEdge.getSelectedIndex() != -1) {
            Edge selEdge = (Edge) frame.cbEdge.getSelectedItem();
            frame.btEdgeColor.setBackground(selEdge.getColor());
            frame.btEdgeColor.setForeground(selEdge.getColor());
            frame.cbEdgeStart.setSelectedItem(selEdge.getStartNode());
            frame.cbEdgeEnd.setSelectedItem(selEdge.getStopNode());
            frame.cbEdgeMnRemove.setModel(new DefaultComboBoxModel(
                    selEdge.getMiddleNodes().toArray(new Node[0])));
        }
    }

    /**
     * Deletes the node and refreshes the gui.
     */
    public void deleteNode() {
        Node selNode = (Node) frame.cbNodes.getSelectedItem();
        engine.removeNode(selNode.getId());
        nodesPanelSelected(); // The whole node panel has to be refreshed
    }

    /**
     * Updates a node and refreshed the gui.
     */
    public void updateNode() {
        Node selNode = (Node) frame.cbNodes.getSelectedItem();
        engine.setNodeName(selNode.getId(), frame.tfNodeName.getText());
        engine.setNodePos(selNode.getId(), Float.parseFloat(frame.tfNodeX.getText()),
                Float.parseFloat(frame.tfNodeY.getText()));
        fullMap();
    }
}
